/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * RSPerspective.cpp
 *
 *  Created on: 2010/03/08
 *      Author: Alexandre Pires ( c.alexandre.pires@gmail.com )
 */

#include "RSPerspective.h"
#include "RSValueElement.h"
#include "RSFloat.h"
#include <math.h>

namespace Realsphere {

    RSPerspective::RSPerspective ( const QString& rScopeId ) :
        RSProjectionTechniqueElement ( RS_PERSPECTIVE, rScopeId )
    {
        mHorizontalFieldOfView = NULL;
        mVerticalFieldOfView = NULL;
    }

    RSPerspective::~RSPerspective ()
    {
        mHorizontalFieldOfView = NULL;
        mVerticalFieldOfView = NULL;
    }

    RSFloat* RSPerspective::HorizontalFieldOfView () const
    {
        return mHorizontalFieldOfView;
    }

    RSFloat* RSPerspective::VerticalFieldOfView () const
    {
        return mVerticalFieldOfView;
    }

    RSError RSPerspective::SetHorizontalFieldOfView ( RSFloat* rValue )
    {
        rValue->SetTag ( RS_HORIZONTAL_FIELD_OF_VIEW_TAG );
        RSError error = AddValueElement ( rValue );
        return error;
    }

    RSError RSPerspective::SetVerticalFieldOfView ( RSFloat* rValue )
    {
        rValue->SetTag ( RS_VERTICAL_FIELD_OF_VIEW_TAG );
        RSError error = AddValueElement ( rValue );
        return error;
    }

    RSError RSPerspective::SetHorizontalFieldOfView ( const RSFloat& rValue )
    {
        if ( mHorizontalFieldOfView == NULL )
            return RS_VALUE_UNDEFINED;

        mHorizontalFieldOfView->SetValue ( rValue );

        return RS_NO_ERROR;
    }

    RSError RSPerspective::SetVerticalFieldOfView ( const RSFloat& rValue )
    {
        if ( mVerticalFieldOfView == NULL )
            return RS_VALUE_UNDEFINED;

        mVerticalFieldOfView->SetValue ( rValue );

        return RS_NO_ERROR;
    }

    RSError RSPerspective::SetHorizontalFieldOfView ( const float& rValue )
    {
        RSFloat value ( rValue );
        RSError error = SetHorizontalFieldOfView ( value );
        return error;
    }

    RSError RSPerspective::SetVerticalFieldOfView ( const float& rValue )
    {
        RSFloat value ( rValue );
        RSError error = SetVerticalFieldOfView ( value );
        return error;
    }

    RSError RSPerspective::BeforeAddValueElementValidation ( RSValueElement* rValueElement )
    {
        RSError
                error =
                        RSProjectionTechniqueElement::BeforeAddValueElementValidation ( rValueElement );

        if ( error == RS_INVALID_VALUE )
        {
            if ( rValueElement->Tag () == RS_HORIZONTAL_FIELD_OF_VIEW_TAG
                    || rValueElement->Tag () == RS_VERTICAL_FIELD_OF_VIEW_TAG )
            {
                error = RS_NO_ERROR;
            }
        }

        return error;
    }

    RSError RSPerspective::BeforeRemoveValueElementValidation ( RSValueElement* rValueElement )
    {
        RSError
                error =
                        RSProjectionTechniqueElement::BeforeRemoveValueElementValidation ( rValueElement );

        if ( error == RS_INVALID_VALUE )
        {
            if ( rValueElement->Tag () == RS_HORIZONTAL_FIELD_OF_VIEW_TAG
                    || rValueElement->Tag () == RS_VERTICAL_FIELD_OF_VIEW_TAG )
            {
                error = RS_NO_ERROR;
            }
        }

        return error;
    }

    bool RSPerspective::AfterAddValueElementValidation ( RSValueElement* rValueElement )
    {
        bool
                handled =
                        RSProjectionTechniqueElement::AfterAddValueElementValidation ( rValueElement );

        if ( !handled )
        {

            RSFloat* value = RSFloatCast ( rValueElement );

            if ( rValueElement->Tag () == RS_HORIZONTAL_FIELD_OF_VIEW_TAG )
            {
                if ( mHorizontalFieldOfView != NULL )
                    RemoveChildElement ( mHorizontalFieldOfView );

                mHorizontalFieldOfView = value;
                handled = true;

                emit HorizontalFieldOfViewChanged ( this, mHorizontalFieldOfView );
            }
            else if ( rValueElement->Tag () == RS_VERTICAL_FIELD_OF_VIEW_TAG )
            {
                if ( mVerticalFieldOfView != NULL )
                    RemoveChildElement ( mVerticalFieldOfView );

                mVerticalFieldOfView = value;
                handled = true;

                emit VerticalFieldOfViewChanged ( this, mVerticalFieldOfView );
            }
        }

        return handled;
    }

    bool RSPerspective::AfterRemoveValueElementValidation ( RSValueElement* rValueElement )
    {
        bool
                handled =
                        RSProjectionTechniqueElement::AfterRemoveValueElementValidation ( rValueElement );

        if ( !handled )
        {

            if ( rValueElement->Tag () == RS_HORIZONTAL_FIELD_OF_VIEW_TAG )
            {
                mHorizontalFieldOfView = NULL;
                handled = true;
                emit HorizontalFieldOfViewChanged ( this, mHorizontalFieldOfView );
            }
            else if ( rValueElement->Tag () == RS_VERTICAL_FIELD_OF_VIEW_TAG )
            {
                mVerticalFieldOfView = NULL;
                handled = true;
                emit VerticalFieldOfViewChanged ( this, mVerticalFieldOfView );
            }
        }

        return handled;
    }

    QMatrix4x4 RSPerspective::ProjectionMatrix () const
    {
        float left = -1;
        float right = 1;
        float top = 1;
        float bottom = -1;

        float z_near = RS_DEFAULT_Z_NEAR;
        float z_far = RS_DEFAULT_Z_FAR;

        RSFloat* znear_value_element = ZNear ();
        RSFloat* zfar_value_element = ZFar ();

        if ( znear_value_element != NULL && zfar_value_element != NULL )
        {
            z_near = znear_value_element->Value ();
            z_far = zfar_value_element->Value ();
        }

        RSFloat* aspect_ratio_value_element = AspectRatio ();

        if ( aspect_ratio_value_element != NULL )
        {

            float aspect_ratio = aspect_ratio_value_element->Value ();

            if ( mHorizontalFieldOfView != NULL )
            {
                right = tan ( mHorizontalFieldOfView->Value () * RS_DEG_TO_RAD
                        * 0.5f ) * z_near;
                top = ( right * aspect_ratio );
            }
            else if ( mVerticalFieldOfView != NULL )
            {
                top = tan ( mVerticalFieldOfView->Value () * RS_DEG_TO_RAD
                        * 0.5f ) * z_near;
                right = ( top * aspect_ratio );
            }

        }
        else if ( mHorizontalFieldOfView != NULL && mVerticalFieldOfView
                != NULL )
        {
            right = tan ( mHorizontalFieldOfView->Value () * RS_DEG_TO_RAD * 0.5f )
                    * z_near;
            top = tan ( mVerticalFieldOfView->Value () * RS_DEG_TO_RAD * 0.5f )
                    * z_near;
        }

        left = -right;
        bottom = -top;

        QMatrix4x4 projection;
        projection.frustum ( left, right, bottom, top, z_near, z_far );
        return projection;
    }
}
